/*
 * $Id: ProtocolChecker.java 1259 2007-01-09 14:23:47Z tcoupaye $
 *
 * Behavior Protocols extensions for static and runtime checking
 * developed for the Julia implementation of Fractal.
 *
 * Copyright (C) 2006
 *    Formal Methods In Software Engineering Group
 *    Institute of Computer Science
 *    Academy of Sciences of the Czech Republic
 *
 * Copyright (C) 2006 France Telecom
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * Contact: ft@nenya.ms.mff.cuni.cz
 * Authors: Tomas Bures <bures@nenya.ms.mff.cuni.cz>
 *
 */

package org.objectweb.fractal.bpc.staticchecker;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.Interface;
import org.objectweb.fractal.api.NoSuchInterfaceException;
import org.objectweb.fractal.api.control.BindingController;
import org.objectweb.fractal.api.control.ContentController;
import org.objectweb.fractal.api.type.InterfaceType;
import org.objectweb.fractal.bpc.ProtocolController;
import org.objectweb.fractal.util.Fractal;


/**
 * Fractal frontend for the protocol checker.
 */
public class ProtocolChecker {
    
    /**
     * Returns the component name. Uses its name controller to obtain the name. If the name is null or the
     * name controller is not present, computes the name.  
     * @param comp The component
     * @return Component name
     */
    private static String getComponentName(Component comp) {
        String name;
        try {
            name = Fractal.getNameController(comp).getFcName();
            
            if (name==null) {
                name = comp.toString();
            }
        } catch (Exception exc) {
            // The component does not have a name assigned, this isn't actually a problem. Just the eventuall error message won't be so nice.
            name = comp.toString();
        }
        
        return name;
    }
    
    /**
     * Constructs description of interfaces for a specified component.
     * 
     * @param comp Component which interfaces are inspected.
     * @param frameName Name of the component.
     * @return Array containing the interface descriptions.
     * @throws ProtocolCheckerException
     */
    private static InterfaceInstance[] getInterfaceInstances(Component comp, String frameName) throws ProtocolCheckerException {
        LinkedList iis=new LinkedList();
        
        Object[] ifaces=comp.getFcInterfaces();
        
        for (int ifaceIdx=0; ifaceIdx<ifaces.length; ifaceIdx++) {
            Interface iface=(Interface)ifaces[ifaceIdx];

            String itfName=iface.getFcItfName();
            
            boolean isControl = itfName.startsWith("/") || itfName.equals("component") || itfName.endsWith("-controller");

            if (!isControl) {
	            boolean isProvided=!((InterfaceType)iface.getFcItfType()).isFcClientItf();
	            
	            String ifaceSigName=((InterfaceType)iface.getFcItfType()).getFcItfSignature();
	            Class ifaceSig;
	            try {
	                ifaceSig = Class.forName(ifaceSigName);
	            } catch (ClassNotFoundException e) {
	                throw new ProtocolCheckerException("Can't load class "+ifaceSigName+".");
	            }
	            
	            Method[] ifaceMethods=ifaceSig.getMethods();
	            HashSet methodNames=new HashSet();
	            
	            for (int mthIdx=0; mthIdx<ifaceMethods.length; mthIdx++) {
	                String mthName=ifaceMethods[mthIdx].getName();
	                
	                if (methodNames.contains(mthName)) {
	                    throw new ProtocolCheckerException("Duplicate method name "+mthName+" in interface "+ifaceSigName+".");
	                }
	                methodNames.add(mthName);
	            }
	            
	            InterfaceInstance ii=new InterfaceInstance(frameName, itfName, isProvided, (String[])methodNames.toArray(new String[ifaceMethods.length]));
	            
	            iis.add(ii);
            }
        }
        
        return (InterfaceInstance[])iis.toArray(new InterfaceInstance[0]);
    }

    /**
     * Checks a protocol of a nested component.
     * Traverses the whole component hierarchy starting from <tt>rootComp</tt>. On every level checks for compliance of the container
     * frame protocol and the architecture protocol created from the frame protocols of the direct subcomponents.
     *
     * @param rootComp The root component where to start to protocol checking.
     * @return Result of the check.
     */
    public static NestedCheckingResult check(Component rootComp) throws ProtocolCheckerException {
        
        // Get the content controller of the root component
        ContentController rootContentController;
        try {
            rootContentController = Fractal.getContentController(rootComp);
        } catch (NoSuchInterfaceException exc) {
            // The component is obviously primitive, so don't check it and tell that it is OK.
            return new NestedCheckingResult();
        }
        
        // Get the name of the root component
        String rootName = getComponentName(rootComp);
        
        // Get the frame protocol of the root component
        String frameProtocol;
        int frameOrder;
        try {
            ProtocolController protoCont=(ProtocolController)rootComp.getFcInterface("protocol-controller");
            frameProtocol = protoCont.getFcProtocol();
            frameOrder = protoCont.getFcOrder();
        } catch (Exception exc) {
            frameProtocol = null;
            frameOrder = 0;
        }
//        if (frameProtocol == null) {
//            // The frame protocol of the rootComponent is not assigned. This isn't necessarily an error, however returning ERR_OK is somewhat inappropriate.
//            return new NestedCheckingResult(rootName,NestedCheckingResult.ERR_NOT_CHECKED,"The protocol is missing.");
//        }
        
        // Collect the delegations to the sub-components
        List bindings = new LinkedList();
        
        // Get the binding controller of the root component
        try {
            BindingController rootBindingController = Fractal.getBindingController(rootComp);
            
            String[] bindingNames = rootBindingController.listFc();
            for (int nmIdx=0; nmIdx<bindingNames.length; nmIdx++) {
                try {
                    Interface bindingIface = (Interface)rootBindingController.lookupFc(bindingNames[nmIdx]);
                    // System.out.println("Looking for iface " + bindingNames[nmIdx]);

                    // We assume that it is a delegation if there is external interface of the same name that is provided.
                    boolean isDelegation=!((InterfaceType)((Interface)rootComp.getFcInterface(bindingNames[nmIdx])).getFcItfType()).isFcClientItf();
                    
                    String boundCompName;
                    if (isDelegation) {
                        bindings.add(new Binding(null, bindingNames[nmIdx], getComponentName(bindingIface.getFcItfOwner()), bindingIface.getFcItfName()));
                    }
                    
                    
                } 
                catch (NoSuchInterfaceException lookupExc) {
                    //assert(false);
                }
                catch (NullPointerException lookupExc) {
					//julia bug with non-existing binding that should be reported earlier - JK
					//assert(false);
                }
            }
            
            
        } catch (NoSuchInterfaceException exc) {
            // It is strange indeed that a composite component does not implement the binding controller. However, it is no big harm for us.
        }
        
        
        // Collect the protocols of the subcomponents as well as their bindings
        List subFInsts = new LinkedList();
        StringBuffer subCompProtWarnings=new StringBuffer();
        boolean anySubCompWithProt=false;
        boolean anySubCompWithoutProt=false;
        
        Component[] subComponents = rootContentController.getFcSubComponents();
        for (int subIdx=0; subIdx<subComponents.length; subIdx++) {
            Component subComponent = subComponents[subIdx];
            
            // Get the name of the subcomponent
            String subName = getComponentName(subComponent);
            
            // Get the frame protocol of the subcomponent
            String subFrameProtocol;
            int subFrameOrder;
            try {
                ProtocolController subProtoCont=(ProtocolController)subComponent.getFcInterface("protocol-controller");
                subFrameProtocol = subProtoCont.getFcProtocol();
                subFrameOrder = subProtoCont.getFcOrder();
            } catch (Exception exc) {
                subFrameProtocol = null;
                subFrameOrder = 0;
            }
            if (subFrameProtocol == null) {
//                // The sub-component protocol is missing, this is a serious problem.
//                return new NestedCheckingResult(rootName + "/" + subName, NestedCheckingResult.ERR_BADACTIVITY, "The sub-component protocol is missing");
                subCompProtWarnings.append("The sub-component protocol is missing for " + rootName + "/" + subName + ".\n");
                anySubCompWithoutProt=true;
            } else {
                anySubCompWithProt=true;
            }
            
            // Add the component description to the list of sub-component descriptions
            subFInsts.add(new FrameInstance(subName, getInterfaceInstances(subComponent, subName), subFrameProtocol, subFrameOrder));
            
            // If the sub-component is composite, verify it's compliance
            ContentController subContentController;
            try {
                subContentController = Fractal.getContentController(subComponent);
                
                NestedCheckingResult subCheck = check(subComponent);
                if (subCheck.getErrorType() != CheckingResult.ERR_OK) {
                    // Prepend the name of this component to the error result location so eventually we get the full path to the faulty component
                    subCheck.errorLocation = rootName + "/" + subCheck.errorLocation;
                    return subCheck;
                }
            } catch (NoSuchInterfaceException exc) {
                // The component is obviously primitive, so don't descent into it.
            }
            
            // Add bindings that origin in the subcomponent
            BindingController subBindingController;
            try {
                subBindingController = Fractal.getBindingController(subComponent);
                
                String[] subBindingNames = subBindingController.listFc();
                for (int nmIdx=0; nmIdx<subBindingNames.length; nmIdx++) {
                    try {
//                        System.err.println("Interface "+subName+" : "+subBindingNames[nmIdx]);
                        Interface subBindingIface = (Interface)subBindingController.lookupFc(subBindingNames[nmIdx]);

                        // Process the interface only if it is bound (i.e., don't process unbound optional interfaces)
                        if (subBindingIface!=null) {
	                        // We assume that it is a binding if there is an external interface of the same name that is required.
	                        boolean isBinding=((InterfaceType)((Interface)subComponent.getFcInterface(subBindingNames[nmIdx])).getFcItfType()).isFcClientItf();
	                        
	                        boolean isSubsumption=subBindingIface.getFcItfOwner()==rootComp;
	                        
	                        // Traverse only the bindings that are on the first level of nesting. The binding controller gives us also the internal client interfaces.
	                        // Notice that checking isFcItfInternal doesn't work in this case since the interface object is external with respect to the sub-component.
	                        if (isBinding || isSubsumption) {
	                            String subBoundCompName;
	                            if (isSubsumption) {
	                                subBoundCompName=null;
	                            } else {
	                                subBoundCompName=getComponentName(subBindingIface.getFcItfOwner());
	                            }
	                            
	                            bindings.add(new Binding(subName,subBindingNames[nmIdx],subBoundCompName,subBindingIface.getFcItfName()));
	                        }
                        }
                        
                    } catch (NoSuchInterfaceException lookupExc) {
                        //assert(false);
                    }
                }
                
            } catch (NoSuchInterfaceException exc) {
                // We don't mind if the sub-component doesn't implement the binding controller. It can be just because it has no client interfaces.
            }
            
        }
        
        if (frameProtocol!=null) {
	        if (anySubCompWithProt) {
	            System.out.println();
	            System.out.print(subCompProtWarnings);
	        }
	        
	        if (!anySubCompWithoutProt) {
                System.out.println("Checking component " + getComponentName(rootComp) + "...");
		        FrameInstance rootFInst = new FrameInstance(null, getInterfaceInstances(rootComp, null), frameProtocol, frameOrder);
		        
		        CheckingResult oneLevelCheck = Checker.check(
		                rootFInst,
		                (FrameInstance[])subFInsts.toArray(new FrameInstance[subFInsts.size()]),
		                (Binding[])bindings.toArray(new Binding[bindings.size()])
		        );
		        
		        if (oneLevelCheck.getErrorType() != CheckingResult.ERR_OK) {
                    System.out.println("\t\t[FAILED]");
		            return new NestedCheckingResult(rootName,oneLevelCheck);
		        }
                else
                    System.out.println("\t\t[  OK  ]");
	        }
        }
        
        return new NestedCheckingResult();
    }
    
    
}
